#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <tool/ysortedList.h>
#include <tool/yoneWayList.h>
#include <tool/ypropertiesFile.h>

#ifdef  UNIT_LINE_SIZE
#undef  UNIT_LINE_SIZE
#endif
/** Line size max read, from source file */
#define UNIT_LINE_SIZE            120000

/** Key Data struct, declare beforehand for use */
typedef struct keyData keyData;

/** Key Data struct */
struct keyData {
  /** The key, a string. */
  char *key;
  /** List of values. Each value is a string. */
  oneWayList valueList;
};

/** Properties file data struct */
struct propertiesFileData {
  /** Contain value of sizeof(struct keyData) */
  unsigned int sizeof_keyData;
  /** Type <code>strFn</code> */
  strFn strFn;
  /** If type <code>strFn</code> was pass in and 
    not created by library's function. */
  boolean setStrFn;
  /** list of key & their data found in the properties file. */
  sortedList keyList;
  /** The file to work with. */
  char *file;
  /** Empty <code>keyData</code> for comparison use. */
  keyData *cmpKeyData;
  /** Flag to tell function to translate escape sequence in string */
  boolean flagDoEscapeSequence;
};

/*======= declare internal function */
boolean ypropertiesFile_free(propertiesFile *objP);
/* specific operation */
char *ypropertiesFile_getFilename(const propertiesFile obj);
char **ypropertiesFile_getKeys(const propertiesFile obj, 
    unsigned int *numKeys);
char *ypropertiesFile_getString(propertiesFile obj, char *key);
char **ypropertiesFile_getStringArray(propertiesFile obj, 
    char *key, unsigned int *len);
char *ypropertiesFile_getStringES(propertiesFile obj, char *key);
char **ypropertiesFile_getStringArrayES(propertiesFile obj, 
    char *key, unsigned int *len);
/*--- function ONLY for internal use ---*/
/* keyData */
keyData *ypropertiesFile_createKeyData(const propertiesFile obj, 
    const char *key, const size_t keyLen, 
    const char *value, const size_t valueLen);
boolean ypropertiesFile_freeKeyData(void **aKeyDataP);
g_reply ypropertiesFile_compareKeyData(const void *aKeyData1, 
    const void *aKeyData2);
boolean ypropertiesFile_doEqualAction(sortedList list, 
    void *anItem, void *aControlObj);
/* keyData valueList specific */
boolean ypropertiesFile_freeValueData(void **aValueP);
void *ypropertiesFile_valueClone(const void *anItem, 
    const void *aControlObj);

/*======= directly exposed function */
/** Create a properties file struct and 
provide function to access properties file content.
@param file the file to work with.
  MUST provide the relative path or the absolute path to the file and 
  its' full name.
@return a working newly created properties file struct 
  or <code>NULL</code> if failure.
*/
propertiesFile ypropertiesFile_create(char *file) {
  return ypropertiesFile_createWith(file, NULL);
}

/** Create a properties file struct and 
provide function to access properties file content.
<code>propertiesFile</code> will NOT free the memory of 
<code>strFn</code> that is created externally, 
you MUST free this yourself.
@param file the file to work with.
  MUST provide the relative path or the absolute path to the file and 
  its' full name.
@param strFn pass in the <code>strFn</code> to use.
@return a working newly created properties file struct 
  or <code>NULL</code> if failure.
*/
propertiesFile ypropertiesFile_createWith(char *file, strFn strFn) {
  /* try open file */
  FILE *sourceFile = fopen(file, "r");
  if(sourceFile == NULL) { /* can not open file */
    return NULL;
  }
  
  propertiesFile obj;
  FN_MALLOC_ERRMSG(obj, sizeof(struct propertiesFile), 
      "Error: ypropertiesFile_createWith(char *, strFn) malloc(obj)")
  if (obj == NULL) {return NULL;}
  FN_MALLOC_ERRMSG(obj->d, sizeof(struct propertiesFileData), 
      "Error: ypropertiesFile_createWith(char *, strFn) malloc(obj->d)")
  if (obj->d == NULL) {ypropertiesFile_free(&obj); return NULL;}
  /*--- data ---*/
  obj->d->sizeof_keyData = sizeof(struct keyData);
  /* strFn */
  if (strFn == NULL) {
    obj->d->strFn = ystr_createFn();
    if (obj->d->strFn == NULL) {
      perror("Error: ypropertiesFile_createWith(char *, strFn) obj->d->strFn == NULL");
      ypropertiesFile_free(&obj);
      return NULL;
    }
    obj->d->setStrFn = false;
  } else {
    obj->d->strFn = strFn;
    obj->d->setStrFn = true;
  }
  /* keyList */
  obj->d->keyList = 
      ysortedList_create(ypropertiesFile_freeKeyData, ypropertiesFile_compareKeyData);
  if (obj->d->keyList == NULL) {
    perror("Error: ypropertiesFile_createWith(char *, strFn) obj->d->keyList == NULL");
    ypropertiesFile_free(&obj);
    return NULL;
  }
  if (obj->d->keyList->setFnDoEqualAction(
        obj->d->keyList, ypropertiesFile_doEqualAction) == false) {
    perror("Error: ypropertiesFile_createWith(..) obj->d->keyList->setFnDoEqualAction(..) == false");
    ypropertiesFile_free(&obj);
    return NULL;
  }
  /* remember the file variable 
    do NOT point to the external string! Encapsulate & create another. */
  obj->d->file = obj->d->strFn->clone(obj->d->strFn, file);
  /* keyData made specially for comparison,
    it's an empty keyData, NULL as key & an empty value list */
  obj->d->cmpKeyData = ypropertiesFile_createKeyData(obj, NULL, 0, NULL, -1);
  if (obj->d->cmpKeyData == NULL) {
    perror("Error: ypropertiesFile_createWith(..) ypropertiesFile_createKeyData(..) == NULL");
    ypropertiesFile_free(&obj);
    return NULL;
  }

  /* START: initialize the data */
  char *line, *cur, *key, *value;
  unsigned int keyLen;
  FN_MALLOC_ERRMSG(line, UNIT_LINE_SIZE * sizeof(char), 
      "Error: ypropertiesFile_createWith(char *, strFn) malloc(line)")
  if (line == NULL) {ypropertiesFile_free(&obj); return NULL;}
  while (fgets(line, UNIT_LINE_SIZE, sourceFile) != NULL) {
    cur = obj->d->strFn->trimLeft(line, UNIT_LINE_SIZE);
    if (*cur == '#') { 
      /* a comment line, do nothing 
        reset line with NULL char */
      memset(line, (int) NULL, UNIT_LINE_SIZE);
    } else if (*cur == '\n' || *cur == '\r') { 
      /* an empty line 
        reset line with NULL char, 
        length to & inclusive of char at cur pointer position */
      memset(line, (int) NULL, cur - line +1);
    } else { 
      /* has something */
      /*--- the key */
      key = cur;
      do {
        cur++; /* next char */
      } while (*cur != ' ' && *cur != '\t' && *cur != '=' 
          && *cur != '\n' && *cur != '\r');
      /* found the beginning seperator at cur */
      /* start: from char at pointer key
        length: excludes cur pointer position */
      keyLen = (cur - key);

      /*--- the seperator 
        1. an equal sign '=' with zero to many spaces in front & back,
        or 2. only spaces ' ' with out any equal sign. e.g. 
        "KEY=VALUE", "KEY  =  VALUE", "KEY= VALUE", "KEY =VALUE"
        "KEY VALUE", "KEY  VALUE" */
      if (*cur != '\n' && *cur != '\r') {
        if (*cur == '=') { /* found an equal sign '=',
          skip spaces & horizontal tabs only */
          do {
            cur++; /* next char */
          } while (*cur == ' ' || *cur == '\t');
        } else { /* look for an equal sign '=' & skip spaces & horizontal tabs */
          do {
            cur++; /* next char */
          } while ((*cur == ' ' || *cur == '\t') && *cur != '=');
          if (*cur == '=') {/* found an equal sign '=',
            skip spaces & horizontal tabs only */
            do {
              cur++; /* next char */
            } while (*cur == ' ' || *cur == '\t');
          }
        } /* if (*cur == '=') */
      } /* if (*cur != '\n' && *cur != '\r') */

      /*--- the value */
      if (*cur != '\n' && *cur != '\r') {
        /* found the start char of value at cur */
        value = cur;
        do {
          cur++; /* next char */
        } while (*cur != '\n' && *cur != '\r');
        /* found the return or new line char at cur 
        
          create this keyData
          value start: from char at pointer value
          value length: excludes cur pointer position
          
          add this item into the list, 
          NOTE: an item creation here! 
            if item already exist in list then
            free this extra item in ypropertiesFile_doEqualAction(..) 
            but store this item's new value. */
        obj->d->keyList->addItem(
            obj->d->keyList, 
            (void *) ypropertiesFile_createKeyData(obj, 
                key, keyLen, value, (cur - value)), 
            (void *) obj);
      } else {
        /* no value found 
        
          add this item into the list, 
          NOTE: an item creation here! 
            if item already exist in list then
            free this extra item in ypropertiesFile_doEqualAction(..) 
            but store this item's new value. */
        obj->d->keyList->addItem(
            obj->d->keyList, 
            (void *) ypropertiesFile_createKeyData(obj, 
                key, keyLen, "", 0), 
            (void *) obj);
      }
      /* reset line with NULL char, 
        length to & inclusive of char at cur pointer position */
      memset(line, (int) NULL, cur - line +1);
    }
  }
  free(line);
  fclose(sourceFile); /* close: source file */
  /* END: initialize the data */
  
  /*--- init function pointers ---*/
  obj->free = ypropertiesFile_free;
  /* specific operation */
  obj->getFilename = ypropertiesFile_getFilename;
  obj->getKeys = ypropertiesFile_getKeys;
  obj->getString = ypropertiesFile_getString;
  obj->getStringArray = ypropertiesFile_getStringArray;
  obj->getStringES = ypropertiesFile_getStringES;
  obj->getStringArrayES = ypropertiesFile_getStringArrayES;
  return obj;
}

/*======= exposed through function pointers */
/** Free a <code>propertiesFile</code>, free up the memory.
@param objP pass in the address of <code>propertiesFile</code> to be free.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean ypropertiesFile_free(propertiesFile *objP) {
  /* NOT critical if propertiesFile is NULL, assume all's fine */
  if (*objP == NULL) {return true;}
  if ((*objP)->d != NULL) {
    /* free strFn ONLY if it is created within library's function */
    if ((*objP)->d->setStrFn == false
        && (*objP)->d->strFn != NULL
        && (*objP)->d->strFn->freeFn(&(*objP)->d->strFn) == false) {
      /* possible memory leak */
      perror("Error: ypropertiesFile_free(..) ..(*objP)->d->strFn->freeFn(&(*objP)->d->strFn) == false");
    }
    /* free the key list */
    if ((*objP)->d->keyList != NULL
        && (*objP)->d->keyList->free(&(*objP)->d->keyList) == false) {
      /* possible memory leak */
      perror("Error: ypropertiesFile_free(..) ..(*objP)->d->keyList->free(&(*objP)->d->keyList) == false");
    }
    /* free keyData made specially for comparison */
    if ((*objP)->d->cmpKeyData != NULL) {
      /* HACK: must clear this key, instead of using free(void *) 
        because the key is an external string, not controlled here.
        Also the string is likely a const & NOT malloc(..) */
      (*objP)->d->cmpKeyData->key = NULL;
      if (ypropertiesFile_freeKeyData((void *) &(*objP)->d->cmpKeyData) == false) {
      /* possible memory leak */
      perror("Error: ypropertiesFile_free(..) ..ypropertiesFile_freeKeyData(..) == false");
      }
    }
    /* free file string */
    free((*objP)->d->file);
    /* free propertiesFileData */
    free((*objP)->d);
    (*objP)->d = NULL;
  }
  /* free propertiesFile */
  free(*objP);
  *objP = NULL;
  return true;
}

/** Return a cloned string of the file name & path 
that this <code>propertiesFile</code> is working with.
<p>Since this string is <code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
</p>
@param obj pass in the <code>propertiesFile</code>.
@return Return a cloned string of the file name & path that 
  this <code>propertiesFile</code> is working with
  or <code>NULL</code> if failure.
*/
char *ypropertiesFile_getFilename(const propertiesFile obj) {
  if (obj == NULL || obj->d == NULL) {return NULL;}
  /* clone the string, 
    do NOT allow outside to modify our encapsulated data */
  return obj->d->strFn->clone(obj->d->strFn, obj->d->file);
}

/** Returns a list of the keys in this <code>propertiesFile</code>.
<p>Since this <code>char **</code>, a 2D array is 
<code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
</p>
@param obj pass in the <code>propertiesFile</code>.
@param numKeys the number of keys in this <code>propertiesFile</code>
@return Returns a list of the keys in this <code>propertiesFile</code>,
  or <code>NULL</code> if failure.
*/
char **ypropertiesFile_getKeys(const propertiesFile obj, 
    unsigned int *numKeys) {
  if (obj == NULL || obj->d == NULL) {*numKeys = 0; return NULL;}
  /* if there is something in the list, start from last item */
  if (obj->d->keyList->goLast(obj->d->keyList) == true) {
    /* size of list */
    int listSize = obj->d->keyList->size(obj->d->keyList);
    if (listSize <= 0) {
      perror("Error: ypropertiesFile_getKeys(..) obj->d->keyList->size(..) <= 0");
      *numKeys = 0;
      return NULL;
    }
    /* allocate array of key list */
    char **keys;
    FN_MALLOC_ERRMSG(keys, listSize * sizeof(char *), 
        "Error: ypropertiesFile_getKeys(..) malloc(keys)")
    if (keys == NULL) {*numKeys = 0; return NULL;}
    *numKeys = listSize;
    do {
      listSize--;
      /* clone the key taken from the keyList's current item */
      keys[listSize] = obj->d->strFn->clone(obj->d->strFn, 
          ((keyData *) obj->d->keyList->getItem(obj->d->keyList))->key);
    } while (obj->d->keyList->goBack(obj->d->keyList) == true);
    return keys;
  }
  *numKeys = 0;
  return NULL;
}

/** Gets a string for the given key from this <code>propertiesFile</code>.
<p>Since this string is <code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
</p>
@param obj pass in the <code>propertiesFile</code>.
@param key the key to search for.
@return Return a cloned string for the given key 
  from this <code>propertiesFile</code> or <code>NULL</code> if failure.
*/
char *ypropertiesFile_getString(propertiesFile obj, char *key) {
  if (obj == NULL || obj->d == NULL) {return NULL;}
  /* prepare the key to compare */
  obj->d->cmpKeyData->key = key;
  /* find this item */
  keyData *listKeyData = (keyData *) 
      obj->d->keyList->findItem(obj->d->keyList, obj->d->cmpKeyData);
  /* if NOT found */
  if (listKeyData == NULL) {return NULL;}
  /* if there is a 1st item */
  if (listKeyData->valueList->goFirst(listKeyData->valueList) == true) {
    /* leave string plain, do NOT translate any escape sequence */
    obj->d->flagDoEscapeSequence = false;
    /* return the clone NEVER give out our read only copy */
    return (char *) 
        listKeyData->valueList->getItemClone(
            listKeyData->valueList, (void *) obj);
  }
  return NULL;
}

/** Gets a list of string for the given key from 
this <code>propertiesFile</code>.
<p>Since this <code>char **</code>, a 2D array is 
<code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
</p>
@param obj pass in the <code>propertiesFile</code>.
@param key the key to search for.
@param len the length of the list of string that is returned.
@return Return a cloned string for the given key 
  from this <code>propertiesFile</code> or <code>NULL</code> if failure.
*/
char **ypropertiesFile_getStringArray(propertiesFile obj, 
    char *key, unsigned int *len) {
  if (obj == NULL || obj->d == NULL) {*len = 0; return NULL;}
  /* prepare the key to compare */
  obj->d->cmpKeyData->key = key;
  /* find this item */
  keyData *listKeyData = (keyData *) 
      obj->d->keyList->findItem(obj->d->keyList, obj->d->cmpKeyData);
  /* if NOT found */
  if (listKeyData == NULL) {*len = 0; return NULL;}

  /* if there is a 1st item */
  if (listKeyData->valueList->goFirst(listKeyData->valueList) == true) {
    /* size of list */
    *len = listKeyData->valueList->size(listKeyData->valueList);
    if (*len <= 0) {
      perror("Error: ypropertiesFile_getStringArray(..) listKeyData->valueList->size(..) <= 0");
      *len = 0;
      return NULL;
    }
    /* allocate array of key list */
    char **values;
    FN_MALLOC_ERRMSG(values, *len * sizeof(char *), 
        "Error: ypropertiesFile_getKeys(propertiesFile) malloc(values)")
    /* leave string plain, do NOT translate any escape sequence */
    obj->d->flagDoEscapeSequence = false;
    int i = 0;
    do {
      /* clone the value taken from the valueList's current item */
      values[i] = (char *) 
          listKeyData->valueList->getItemClone(listKeyData->valueList, obj);
      i++;
    } while (listKeyData->valueList->goNext(listKeyData->valueList) == true);
    return values;
    /* return the clone NEVER give out our read only copy */
  }
  *len = 0;
  return NULL;
}

/** Gets a string for the given key from this <code>propertiesFile</code>, 
translate any valid C escape characters into their proper char.
<p>Since this string is <code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
</p>
@param obj pass in the <code>propertiesFile</code>.
@param key the key to search for.
@return Return a cloned string for the given key 
  from this <code>propertiesFile</code> or <code>NULL</code> if failure.
*/
char *ypropertiesFile_getStringES(propertiesFile obj, char *key) {
  if (obj == NULL || obj->d == NULL) {return NULL;}
  /* prepare the key to compare */
  obj->d->cmpKeyData->key = key;
  /* find this item */
  keyData *listKeyData = (keyData *) 
      obj->d->keyList->findItem(obj->d->keyList, obj->d->cmpKeyData);
  /* if NOT found */
  if (listKeyData == NULL) {return NULL;}
  /* if there is a 1st item */
  if (listKeyData->valueList->goFirst(listKeyData->valueList) == true) {
    /* translate escape sequence in string */
    obj->d->flagDoEscapeSequence = true;
    /* return the clone NEVER give out our read only copy */
    return (char *) 
        listKeyData->valueList->getItemClone(
            listKeyData->valueList, (void *) obj);
  }
  return NULL;
}

/** Gets a list of string for the given key from 
this <code>propertiesFile</code>, 
translate any valid C escape characters into their proper char.
<p>Since this <code>char **</code>, a 2D array is 
<code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
</p>
@param obj pass in the <code>propertiesFile</code>.
@param key the key to search for.
@param len the length of the list of string that is returned.
@return Return a cloned string for the given key 
  from this <code>propertiesFile</code> or <code>NULL</code> if failure.
*/
char **ypropertiesFile_getStringArrayES(propertiesFile obj, 
    char *key, unsigned int *len) {
  if (obj == NULL || obj->d == NULL) {*len = 0; return NULL;}
  /* prepare the key to compare */
  obj->d->cmpKeyData->key = key;
  /* find this item */
  keyData *listKeyData = (keyData *) 
      obj->d->keyList->findItem(obj->d->keyList, obj->d->cmpKeyData);
  /* if NOT found */
  if (listKeyData == NULL) {*len = 0; return NULL;}

  /* if there is a 1st item */
  if (listKeyData->valueList->goFirst(listKeyData->valueList) == true) {
    /* size of list */
    *len = listKeyData->valueList->size(listKeyData->valueList);
    if (*len <= 0) {
      perror("Error: ypropertiesFile_getStringArray(..) listKeyData->valueList->size(..) <= 0");
      *len = 0;
      return NULL;
    }
    /* allocate array of key list */
    char **values;
    FN_MALLOC_ERRMSG(values, *len * sizeof(char *), 
        "Error: ypropertiesFile_getKeys(propertiesFile) malloc(values)")
    /* translate escape sequence in string */
    obj->d->flagDoEscapeSequence = true;
    int i = 0;
    do {
      /* clone the value taken from the valueList's current item */
      values[i] = (char *) 
          listKeyData->valueList->getItemClone(listKeyData->valueList, obj);
      i++;
    } while (listKeyData->valueList->goNext(listKeyData->valueList) == true);
    return values;
    /* return the clone NEVER give out our read only copy */
  }
  *len = 0;
  return NULL;
}

/*======= function ONLY for internal use */
/* keyData */
/** Create a keyData with this key and value.
Will create another copy of this key and value (clone of), 
will NOT directly assign the passed in string or data 
into the object.
<p>Allow creation of <code>NULL</code> or empty key for internal use,
such as in the case of key comparision i.e. between two keyData or
finding a given key, so that we need to compare 
keyData from the list against this given key string. 
The key string can be place in this empty keyData and 
the good point is speed wise this object can be continue to be reused.
<code>g_reply compareKeyData(const void *, const void *)</code>
</p>
Assumtion: previous code check all pass in parameter to be valid!
This function is for internal use ONLY, do NOT expose this.
@param key pass in the key (string), 
  pass in <code>NULL</code> to leave this empty for any assignment.
@param keyLen the length of this key to copy, 
  pass in <code>0</code> to leave this empty for any assignment.
@param value the value (string) to add, 
  pass in <code>NULL</code> to create an empty value list.
@param valueLen the length of this value to copy, 
  pass in less than <code>0</code> to create an empty value list or 
  pass in <code>0</code> to create a value list with a "" string.
@return created keyData or <code>NULL</code> if failure.
*/
keyData *ypropertiesFile_createKeyData(const propertiesFile obj, 
    const char *key, const size_t keyLen, 
    const char *value, const size_t valueLen) {
  keyData *aKeyData;
  FN_MALLOC_ERRMSG(aKeyData, obj->d->sizeof_keyData, 
      "Error: ypropertiesFile_createKeyData(..) malloc(akeyData)")
  if (aKeyData == NULL) {return NULL;}
  if (key == NULL || keyLen <= 0) {
    aKeyData->key = NULL;
  } else {
    /* do NOT point to the external string! Encapsulate & create another. */
    aKeyData->key = obj->d->strFn->cloneLen(obj->d->strFn, key, keyLen);
  }
  
  /* slightly slower but safer to test NULL at start */
  if (value == NULL || valueLen < 0) {
    /* create an empty list */
    aKeyData->valueList = yoneWayList_create(ypropertiesFile_freeValueData);
  } else {
    /* if (valueLen > 0) create list with a value string 
      if (valueLen == 0) create list with an empty value string,
      we MUST clone an empty string & not just assign a "" 
      since we'll be using free(void *) to free it.
      free(void *) a const string or one that is not malloc 
      will crash the program. */
    aKeyData->valueList = 
        yoneWayList_createWithItem(
            (void *) obj->d->strFn->cloneLen(obj->d->strFn, value, valueLen), 
            ypropertiesFile_freeValueData);
  }
  /* provide value cloning functionality */
  aKeyData->valueList->setFnItemClone(aKeyData->valueList, ypropertiesFile_valueClone);
  return aKeyData;
}

/** Free a keyData, free up the memory.
Use <code>void</code> to provide customizable function 
for other library, so they can free memory of similar struct 
through commond function call.
i.e. it is NOT declared as 
"<code>void (*free)(keyData **aKeyDataP);</code>", 
or NOT using type <code>keyData *</code>.
Assumtion: previous code check all pass in parameter to be valid!
This function is for internal use ONLY, do NOT expose this.
@param aKeyDataP pass in the address of <code>keyData *</code> to be free.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean ypropertiesFile_freeKeyData(void **aKeyDataP) {
  /* This is NOT checked - because keyData is ONLY for internal use.
  NOT critical if keyData is NULL, assume all's fine 
  if (*aKeyDataP != NULL) {..} 

  free the stored string, key */
  free(((keyData *) *aKeyDataP)->key);
  /* free the value List */
  if (((keyData *) *aKeyDataP)->valueList->free(
        &((keyData *) *aKeyDataP)->valueList) == false) {
    /* possible memory leak */
    perror("Error: ypropertiesFile_freeKeyData(..) ..((keyData *) *aKeyDataP)->valueList->free(..)");
  }
  /* free the keyData struct */
  free((keyData *) *aKeyDataP);
  *aKeyDataP = NULL;
  return true;
}

/** Compare keys in these <code>keyData</code>.
Case sensitive and white space is also compared!
<p>Uses standard 
<code>int strcmp(const char *str1, const char *str2);</code>
for comparison.
<code>str1</code> is key taken from <code>aKeyData1</code>.
<code>str2</code> is key taken from <code>aKeyData2</code>.
</p>
Assumtion: previous code check all pass in parameter to be valid!
This function is for internal use ONLY, do NOT expose this.
@param aKeyData1 pass in the keyData.
@param aKeyData2 pass in the keyData.
@return Returns <code>g_replyEqual</code> if 
  <code>str1</code> and <code>str2</code> are equal. 
  Returns <code>g_replyLesser</code> or <code>g_replyGreater</code>
  if <code>str1</code> is less than or 
  greater than <code>str2</code> respectively.
  Otherwise <code>g_replyFail</code> for failure.
*/
g_reply ypropertiesFile_compareKeyData(const void *aKeyData1, 
    const void *aKeyData2) {
  /* This is NOT checked - because keyData is ONLY for internal use.
  if (aKeyData1 == NULL || aKeyData2 == NULL) {return g_replyFail;} */
  int compare = strcmp(((keyData *) aKeyData1)->key, 
      ((keyData *) aKeyData2)->key);
  if (compare < 0) {
    return g_replyLesser;
  } else if (compare > 0) {
    return g_replyGreater;
  }
  return g_replyEqual;
}

/** A customized function for <code>ysortedList addItem(..)</code>.
Assumtion: previous code check all pass in parameter to be valid!
This function is for internal use ONLY, do NOT expose this.
@param list the sorted list.
@param anItem the item to be added. 
  (found existing & exact word, add this page ONLY, 
  will free memory of anItem object)
@param aControlObj an object that customized function may work with.
@return <code>true</code> if successfully, <code>false</code> otherwise.
*/
boolean ypropertiesFile_doEqualAction(sortedList list, 
    void *anItem, void *aControlObj) {
  /* This is NOT checked - because keyData is ONLY for internal use.
  if (list == NULL || anItem == NULL || anItem == aControlObj) {
    return false;
  }
  found existing & exact key, add this value ONLY
  NOT using any variable,.. save memory & speed? 
  
  following explained:
  . get the value out of anItem's valueList
  . add this into list's current item
  
  which is like:
  (list's current item)->valueList->addItem(
      (list's current item)->valueList, 
      anItem->valueList->getItem(anItem->valueList));
  */
  ((keyData *) list->getItem(list))->valueList->addItem(
      ((keyData *) list->getItem(list))->valueList, 
      ((keyData *) anItem)->valueList->getItemClone(((keyData *) anItem)->valueList, 
          aControlObj));
  /* free memory of anItem object, NOT required */
  ypropertiesFile_freeKeyData(&anItem);
  return true;
}

/* keyData valueList specific */
/** Free a value string, free up the memory.
Use <code>void</code> to provide customizable function 
for other library, so they can free memory of similar struct 
through commond function call.
i.e. it is NOT declared as 
"<code>void (*free)(char **);</code>", 
or NOT using type <code>char *</code>.
Assumtion: previous code check all pass in parameter to be valid!
This function is for internal use ONLY, do NOT expose this.
@param aValueDataP pass in the address of <code>char *</code> to be free.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean ypropertiesFile_freeValueData(void **aValueP) {
  /* value is a string */
  free((char *) *aValueP);
  *aValueP = NULL;
  return true;
}

/** Clone this value (of keyData)
Use <code>void</code> to provide customizable function 
for other library, so they can free memory of similar struct 
through commond function call.
i.e. it is NOT declared as 
"<code>void *(*itemClone)(char *, const void *);</code>", 
or NOT using type <code>char *</code>.
Assumtion: previous code check all pass in parameter to be valid!
This function is for internal use ONLY, do NOT expose this.
@param anItem the item to be cloned.
@param aControlObj an object that customized function may work with,
  e.g. provide cloning ability, other facility and perform other duty.
@return the cloned item.
*/
void *ypropertiesFile_valueClone(const void *anItem, const void *aControlObj) {
  /* This is NOT checked - because keyData is ONLY for internal use.
  if (anItem == NULL || anItem == aControlObj) {return false;} 
  
  translate escape sequence in string? */
  if (((propertiesFile) aControlObj)->d->flagDoEscapeSequence == true) {
    /* translate escape sequence in string */
    return ((propertiesFile) aControlObj)->d->strFn->cloneES(
        ((propertiesFile) aControlObj)->d->strFn, (char *) anItem);
  } else {
    /* leave string plain */
    return ((propertiesFile) aControlObj)->d->strFn->clone(
        ((propertiesFile) aControlObj)->d->strFn, (char *) anItem);
  }
}
